package com.wisecoach.gatewayplus.spring.seata;

import com.wisecoach.gatewayplus.info.GatewayInfo;
import com.wisecoach.gatewayplus.info.GatewayInfoProvider;
import com.wisecoach.gatewayplus.session.GatewayContext;
import com.wisecoach.gatewayplus.session.GatewayContextHolder;
import com.wisecoach.gatewayplus.session.GatewaySession;
import com.wisecoach.gatewayplus.session.GatewaySessionProvider;
import com.wisecoach.gatewayplus.transaction.*;
import io.seata.rm.tcc.api.BusinessActionContext;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInvocation;

import java.lang.reflect.Method;

/**
 * {@code @author:} wisecoach
 * {@code @date:} 2023/9/29 下午2:25
 * {@code @version:} 1.0.0
 */
@Slf4j
public class DefaultTCCLogicExecutor implements TCCLogicExecutor {

    private final TransactionAttributeSource txAttrSource;
    private final TransactionAdvice advice;
    private final TransactionContextProvider transactionContextProvider;
    private final GatewayInfoProvider gatewayInfoProvider;
    private final GatewaySessionProvider gatewaySessionProvider;

    public DefaultTCCLogicExecutor(TransactionAttributeSource txAttrSource,
                                   TransactionAdvice advice,
                                   TransactionContextProvider transactionContextProvider,
                                   GatewayInfoProvider gatewayInfoProvider,
                                   GatewaySessionProvider gatewaySessionProvider) {
        this.txAttrSource = txAttrSource;
        this.advice = advice;
        this.transactionContextProvider = transactionContextProvider;
        this.gatewayInfoProvider = gatewayInfoProvider;
        this.gatewaySessionProvider = gatewaySessionProvider;
    }

    @Override
    public Object tccTry(MethodInvocation invocation) throws Throwable {
        Object obj = invocation.getThis();
        Method method = invocation.getMethod();
        Object[] args = invocation.getArguments();
        TransactionAttribute txAttr = txAttrSource.getTransactionAttribute(method, method.getDeclaringClass());
        if (txAttr == null) {
            return invocation.proceed();
        }
        // 创建交易上下文
        TransactionContext transactionContext = transactionContextProvider.getTransactionContext(txAttr);
        TransactionContextHolder.setContext(transactionContext);
        if (transactionContext.getTransaction().isNewTransaction()) {
            log.info(String.format("开启一个事务: id=%s", TransactionContextHolder.getContext().getTransaction().getId()));
        }
        // 创建gateway上下文
        log.debug("开始创建Gateway");
        GatewayContext oldGateway = GatewayContextHolder.getContext();
        GatewayInfo gatewayInfo = gatewayInfoProvider.getGatewayInfo(obj, method, args);
        GatewaySession gatewaySession = gatewaySessionProvider.getGatewaySession(gatewayInfo);
        GatewayContext context = GatewayContextHolder.getEmptyContext();
        context.setGateway(gatewaySession);
        context.setOldGateway(oldGateway);
        GatewayContextHolder.setContext(context);
        // 执行前
        advice.before(method, args, obj);
        // 执行原方法
        log.debug("开始调用被加强方法");
        Object ret = invocation.proceed();
        log.debug("被加强方法执行完毕");
        return ret;
    }

    @Override
    public void tccCommit(BusinessActionContext context) {
        MethodInvocation invocation = context.getActionContext("invocation", MethodInvocation.class);
        Object obj = invocation.getThis();
        Method method = invocation.getMethod();
        Object[] args = invocation.getArguments();
        TransactionAttribute txAttr = txAttrSource.getTransactionAttribute(method, method.getDeclaringClass());
        if (txAttr == null) {
            throw new RuntimeException("该方法不是处于事务中的方法");
        }
        advice.afterReturning(null, method, args, obj);
        log.debug("本地事务提交完毕");
        // 清空环境
        if (TransactionContextHolder.getContext() != null) {
            // 如果是最外层的新事务，则可以直接释放
            if (TransactionContextHolder.getContext().getTransaction().isNewTransaction()) {
                log.info(String.format("完成一个事务: id=%s", TransactionContextHolder.getContext().getTransaction().getId()));
                TransactionContextHolder.clearContext();
            } else {
                // 如果是老的，则恢复原环境
                TransactionContext transactionContext = TransactionContextHolder.getContext();
                TransactionContextHolder.setContext(transactionContext.getOldTransaction());
            }
        }
        if (GatewayContextHolder.getContext() != null) {
            if (GatewayContextHolder.getContext().getOldGateway() == null) {
                GatewayContextHolder.clearContext();
            } else {
                // 如果是老的，则恢复原环境
                GatewayContext gatewayContext = GatewayContextHolder.getContext();
                GatewayContextHolder.setContext(gatewayContext.getOldGateway());
            }
        }
    }

    @Override
    public void tccRollback(BusinessActionContext context) {
        MethodInvocation invocation = context.getActionContext("invocation", MethodInvocation.class);
        Object obj = invocation.getThis();
        Method method = invocation.getMethod();
        Object[] args = invocation.getArguments();
        TransactionAttribute txAttr = txAttrSource.getTransactionAttribute(method, method.getDeclaringClass());
        if (txAttr == null) {
            throw new RuntimeException("该方法不是处于事务中的方法");
        }
        try {
            advice.throwing(method, args, obj, null);
            log.debug("本地事务回滚完毕");
        } catch (Throwable e) {
            throw new RuntimeException(e);
        } finally {
            // 清空环境
            if (TransactionContextHolder.getContext() != null) {
                // 如果是最外层的新事务，则可以直接释放
                if (TransactionContextHolder.getContext().getTransaction().isNewTransaction()) {
                    log.info(String.format("完成一个事务: id=%s", TransactionContextHolder.getContext().getTransaction().getId()));
                    TransactionContextHolder.clearContext();
                } else {
                    // 如果是老的，则恢复原环境
                    TransactionContext transactionContext = TransactionContextHolder.getContext();
                    TransactionContextHolder.setContext(transactionContext.getOldTransaction());
                }
            }
            if (GatewayContextHolder.getContext() != null) {
                if (GatewayContextHolder.getContext().getOldGateway() == null) {
                    GatewayContextHolder.clearContext();
                } else {
                    // 如果是老的，则恢复原环境
                    GatewayContext gatewayContext = GatewayContextHolder.getContext();
                    GatewayContextHolder.setContext(gatewayContext.getOldGateway());
                }
            }
        }
    }
}
